package com.danicsoft.daide.serverclient;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import com.danicsoft.daide.binary.Connection;
import com.danicsoft.daide.command.Command;
import com.danicsoft.daide.command.CommandParser;
import com.danicsoft.daide.command.MapDefinitionCommand;
import com.danicsoft.daide.command.NowCommand;
import com.danicsoft.daide.command.OrderCommand;
import com.danicsoft.daide.command.ParseException;
import com.danicsoft.daide.command.SupplyOwnershipCommand;
import com.danicsoft.daide.command.OrderSpec;
import com.danicsoft.daide.command.Turn;
import com.danicsoft.daide.historian.TokenHistorian;
import com.danicsoft.daide.token.CommonToken;
import com.danicsoft.daide.token.StandardToken;
import com.danicsoft.daide.token.TokenMap;
import com.danicsoft.daide.token.TokenMap.OrderNotes;
import com.danicsoft.daide.token.TokenMap.Phases;
import com.danicsoft.daide.token.TextException;
import com.danicsoft.daide.token.TokenGroup;
import com.danicsoft.daide.token.TokenParser;

/**
 * An implementation of a "Hold Server". That is to say, it will ignore any
 * orders submitted, and simply maintain the same army positions.
 * 
 * @author Daniel Yule (daniel.yule@gmail.com)
 * @version 1.0
 */
public class DefaultDaideServer extends DaideServer {

	/**
	 * The starting position of the armies.
	 */
	private NowCommand startingNow;

	/**
	 * The starting ownership of supply centres.
	 */
	private SupplyOwnershipCommand startingSco;

	/**
	 * The map defintion for this game.
	 */
	private MapDefinitionCommand mapDefinition;

	/**
	 * The name of the variant we are playing.
	 */
	private String variantName;

	/**
	 * The turn we are currently on.
	 */
	private Turn curTurn;

	/**
	 * Runs a server on the default port.
	 * 
	 * @param args
	 *        Ignored
	 */
	public static void main(String[] args) {
		try {
			System.out.println("Default DAIDE server starting.");
			System.out.println("This server is only meant to be used for debugging, and has no adjudicator.");
			System.out.println("Copyright 2006, Daniel Yule.");
			Server server = new DefaultDaideServer(Connection.DEFAULT_PORT, new File("C:\\Documents and Settings\\Daniel Yule\\Desktop\\Diplomacy\\Server\\VARIANTS"), "Standard");
			server.equals(null);
		}
		catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Create a new server, listening on the given port and loading its
	 * information from the given folder.
	 * 
	 * @param listenPort
	 *        The port to listen for incoming connections on.
	 * @param variantFolder
	 *        The folder where variant information is kept.
	 * @param variantName
	 *        The name of the variant we are looking for.
	 * @throws IOException
	 *         If we could not load from the files needed or could not connect
	 *         to the port specified.
	 */
	public DefaultDaideServer(int listenPort, File variantFolder, String variantName) throws IOException {
		if (!variantFolder.isDirectory()) {
			throw new IllegalArgumentException("The specified file must be a folder.");
		}
		try {
			this.variantName = variantName;
			Map<CommonToken, String> rmMapping;

			File rmFile = new File(variantFolder, variantName + ".REM");
			rmMapping = readRMFile(rmFile);

			TokenMap myMap = new TokenMap(rmMapping);
			TokenParser myParser = new TokenParser(myMap, null, new TokenHistorian());

			File mdfFile = new File(variantFolder, variantName + ".MDF");
			mapDefinition = (MapDefinitionCommand) readCommandFromFile(mdfFile, myParser);

			File nowFile = new File(variantFolder, variantName + ".NOW");
			startingNow = (NowCommand) readCommandFromFile(nowFile, myParser);

			File scoFile = new File(variantFolder, variantName + ".SCO");
			startingSco = (SupplyOwnershipCommand) readCommandFromFile(scoFile, myParser);

			curTurn = startingNow.getTurn();
			
			for(int i = 0; i < powers().size() - 1; i++) {
				new HoldBot(this);
			}
			
			listen(listenPort, myMap);
		}
		catch (FileNotFoundException e) {
			throw new IOException("Could not load all the file required for this variant.");
		}
		catch (ClassCastException e) {
			throw new IOException("File did not contain required command.");
		}
		catch (TextException e) {
			throw new IOException("Could not parse the command in a required file.");
		}

	}

	/**
	 * Reads a text based command from a file.
	 * 
	 * @param commandFile
	 *        The file that contains a command.
	 * @param parser
	 *        The parser to use to read the tokens.
	 * @return A Command, read from the file.
	 * @throws IOException
	 *         If the file could not be read from.
	 * @throws TextException
	 *         If the file was formatted badly.
	 */
	private Command readCommandFromFile(File commandFile, TokenParser parser) throws IOException, TextException {
		FileInputStream iStream = new FileInputStream(commandFile);
		byte[] fileBytes = new byte[iStream.available()];
		iStream.read(fileBytes);
		String fileString = new String(fileBytes);
		TokenGroup group = parser.readFromText(fileString);
		Command c;
		try {
			c = CommandParser.parse(group);
		}
		catch (ParseException e) {
			throw new IOException("Could not parse the command in the file:\n" + e.getMessage());
			
		}
		return c;
	}

	/**
	 * Parses an REM file and creates a mapping from it.
	 * 
	 * @param rmFile
	 *        The file that contains a mapping from integer to string.
	 * @return A Map from the tokens to the strings they represent.
	 * @throws IOException
	 *         If we could not load from the file specified.
	 */
	private Map<CommonToken, String> readRMFile(File rmFile) throws IOException {
		Map<CommonToken, String> rMap = new HashMap<CommonToken, String>();
		FileInputStream iStream = new FileInputStream(rmFile);
		byte[] fileBytes = new byte[iStream.available()];
		iStream.read(fileBytes);
		String fileString = new String(fileBytes);

		Enumeration<Object> lines = new StringTokenizer(fileString);
		lines.nextElement();
		while (lines.hasMoreElements()) {
			String thisLine = (String) lines.nextElement();
			String[] splitty = thisLine.split(":");
			int tokInt = Integer.parseInt(splitty[0], 16);
			rMap.put(new TokenMap.TokenEntry((byte) ((tokInt >> 8) & 0xFF), (byte) (tokInt & 0xFF)), splitty[1].toUpperCase());
		}

		return rMap;
	}

	@Override
	protected NowCommand armyPosition() {
		return startingNow;
	}

	@Override
	protected Turn currentTurn() {
		return curTurn;
	}

	@Override
	protected MapDefinitionCommand mapDefinition() {
		return mapDefinition;
	}

	@Override
	protected String mapName() {
		return variantName;
	}

	@Override
	protected void nextTurn() {
		curTurn = curTurn.clone();
		switch (curTurn.season.getData()) {
			case Phases.AUT:
			case Phases.FAL:
			case Phases.WIN:
				curTurn.season = TokenMap.getPhaseToken(Phases.SPR);
				curTurn.year++;
				break;
			case Phases.SPR:
			case Phases.SUM:
				curTurn.season = TokenMap.getPhaseToken(Phases.FAL);
				break;

		}
		startingNow.setTurn(curTurn);

	}

	@Override
	protected List<StandardToken> powers() {
		return mapDefinition.getPowers();
	}

	@Override
	protected List<OrderCommand> processOrders(Set<OrderSpec> orders) {
		List<OrderCommand> rList = new ArrayList<OrderCommand>();
		for (OrderSpec order : orders) {
			rList.add(new OrderCommand(order, OrderNotes.MBV, curTurn));
		}
		return rList;
	}

	@Override
	protected void startGame() {

	}

	@Override
	protected SupplyOwnershipCommand supplyOwnership() {
		return startingSco;
	}

	@Override
	protected VariantInfo variantInfo() {
		return new VariantInfo() {

			public int getBuildTimeLimit() {
				return -1;
			}

			public int getMovementTimeLimit() {
				return -1;
			}

			public int getRetreatTimeLimit() {
				return -1;
			}

			public int getSyntaxLevel() {
				return 0;
			}

			public boolean isAnyOrdersAccepted() {
				return false;
			}

			public boolean isDeadlineDisconnect() {
				return false;
			}

		};
	}

}
